iT邦幫忙

2024 iThome 鐵人賽

DAY 28
0

上一篇我們已經成功抓取天氣API的資料,這篇就接續下半部分介面布局的設置和存放資料以及程式碼撰寫。
首先,我們一樣先把天氣APP的介面布局設置完成
activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <Spinner
        android:id="@+id/time"
        android:layout_width="220dp"
        android:layout_height="35dp"
        android:background="@drawable/border"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toStartOf="@+id/guideline"
        app:layout_constraintHorizontal_bias="0.486"
        app:layout_constraintStart_toStartOf="@+id/guideline2"
        app:layout_constraintTop_toBottomOf="@+id/main_result_rt"
        app:layout_constraintVertical_bias="0.287" />

    <TextView
        android:id="@+id/main_result_rt"
        android:layout_width="350dp"
        android:layout_height="369dp"
        android:background="@drawable/border"
        android:text="TextView"
        android:gravity="center"
        android:textColor="@color/black"
        android:textSize="20sp"
        android:textStyle="bold"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toStartOf="@+id/guideline"
        app:layout_constraintHorizontal_bias="0.428"
        app:layout_constraintStart_toStartOf="@+id/guideline2"
        app:layout_constraintTop_toTopOf="@+id/guideline3"
        app:layout_constraintVertical_bias="0.069" />

    <Button
        android:id="@+id/main_search_btn"
        android:layout_width="243dp"
        android:layout_height="160dp"
        android:backgroundTint="@color/KAMENOZOK"
        android:text="搜尋"
        android:textSize="30sp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toStartOf="@+id/guideline"
        app:layout_constraintHorizontal_bias="0.458"
        app:layout_constraintStart_toStartOf="@+id/guideline2"
        app:layout_constraintTop_toBottomOf="@+id/main_result_rt"
        app:layout_constraintVertical_bias="0.9" />


    <Spinner
        android:id="@+id/elementName"
        android:layout_width="150dp"
        android:layout_height="35dp"
        android:background="@drawable/border"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toStartOf="@+id/guideline"
        app:layout_constraintHorizontal_bias="0.91"
        app:layout_constraintStart_toStartOf="@+id/guideline2"
        app:layout_constraintTop_toBottomOf="@+id/main_result_rt"
        app:layout_constraintVertical_bias="0.115" />

    <Spinner
        android:id="@+id/locationName"
        android:layout_width="150dp"
        android:layout_height="35dp"
        android:background="@drawable/border"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toStartOf="@+id/guideline"
        app:layout_constraintHorizontal_bias="0.116"
        app:layout_constraintStart_toStartOf="@+id/guideline2"
        app:layout_constraintTop_toBottomOf="@+id/main_result_rt"
        app:layout_constraintVertical_bias="0.115" />
        
</androidx.constraintlayout.widget.ConstraintLayout>

介面設置完成,會長這樣
https://ithelp.ithome.com.tw/upload/images/20241006/20168805YebmqY4Nbp.png
我解釋一下,在抓取天氣 API 的資料中,設計了三個重要的 Spinner 元件,分別用來呈現「縣市地區 (locationName)」、「天氣因子 (elementName)」以及「時間範疇 (time)」,這些元素以縝密的順序進行排列,從地域資訊的「縣市地區」,到氣象條件的「天氣因子」,再到時間跨度的「今日、明日或後日」,以便使用者能輕鬆篩選並了解所需的天氣資訊。
存放Spinner假資料的地方,在(res/values)下的,紅框strings.xml這個頁面
https://ithelp.ithome.com.tw/upload/images/20241006/2016880512gcL1f8cO.png
利用string-array存放Spinner假資料,我創建四個 Spinner 的假資料陣列,分別是 location_data、element_data、time_data 和 tw_element,這些陣列資料將在應用中提供給 Spinner 作為選項,方便讓String[]抓取資料。
strings.xml

<string-array name="陣列的名字">
        <item>要存放的資料</item>
        <item name="item_1">也可以給item名字,但不是必要</item>
    </string-array>

    <string-array name="location_data">
        <item>臺北市</item>
        <item>新北市</item>
        <item>基隆市</item>
        <item>桃園市</item>
        <item>新竹縣</item>
        <item>新竹市</item>
        <item>苗栗縣</item>
        <item>臺中市</item>
        <item>彰化縣</item>
        <item>南投縣</item>
        <item>雲林縣</item>
        <item>嘉義縣</item>
        <item>嘉義市</item>
        <item>臺南市</item>
        <item>高雄市</item>
        <item>屏東縣</item>
        <item>宜蘭縣</item>
        <item>花蓮縣</item>
        <item>臺東縣</item>
        <item>澎湖縣</item>
        <item>金門縣</item>
        <item>連江縣</item>
    </string-array>

    <string-array name="element_data">
        <item>Wx</item>
        <item>PoP</item>
        <item>MinT</item>
        <item>CI</item>
        <item>MaxT</item>
        <item>All</item>
    </string-array>

    <string-array name="time_data">
        <item>今天</item>
        <item>明天</item>
        <item>後天</item>
    </string-array>

    <string-array name="tw_element">
        <item>當日天氣氣象:</item>
        <item>當日降雨機率:</item>
        <item>當日最底溫度:</item>
        <item>當日舒適度:</item>
        <item>當日最高溫度:</item>
    </string-array>

了解布局後,現在開始連接程式碼,我會簡單說明一下每組程式碼,天氣API的用途
weatherResponse.Java

public class weatherResponse {
    public Records records;
    public class Records{
        public List<Location> location;
    }
    public class Location{
        public String locationName;
        public List<WeatherElement> weatherElement;
    }
    public class WeatherElement{
        public String elementName;
        public List<Time> time;
    }
    public class Time{
        public Parameter parameter;
    }
    public class Parameter{
        public String parameterName;
        public String parameterUnit;
    }

    public String getDataByTime(Integer index,Integer day) {
        return records.location.get(0).weatherElement.get(index).time.get(day).
                parameter.parameterName;
    }
    public String getUnitByTime(Integer index){
        return records.location.get(0)
        .weatherElement.get(0).time.get(index).parameter.parameterUnit;
    }
    public Integer getElementSize(){
        return records.location.get(0).weatherElement.size();
    }
}

這組程式碼,表示從天氣 API 抓取的資料結構
首先宣告 records 是他存取天氣 API 的所有資料
https://ithelp.ithome.com.tw/upload/images/20241006/20168805eRYHWAHMQi.png
往下,每個class都有包含他需要資料,例如:
Location 包含地點名稱(locationName)和一個 WeatherElement 列表,WeatherElement 代表該地點的不同天氣因子(例如溫度、濕度)
WeatherElement 包含天氣因子的名稱(elementName)和一個 Time 列表,Time 代表不同時間點的天氣資料、時間段 (time)
https://ithelp.ithome.com.tw/upload/images/20241006/20168805FBNsPLdQWj.png
getDataByTime:根據天氣因子的索引和時間索引,返回對應的天氣資料。
getUnitByTime:根據時間索引,返回天氣單位。
getElementSize:返回該地點的天氣因子數量。
weatherResponse這組程式碼,會根據使用者選擇提取特定天氣資料。
GetApi.Java

public interface GetApi {
    @GET("F-C0032-001")
    Observable<weatherResponse> getWeatherApi(
            @Query("Authorization") String Authorization,
            @Query("locationName") String locationName,
            @Query("elementName") String elementName
    );
}

GetApi 介面,用來向 /F-C0032-001 這個 API 端點發送 GET 請求,並透過查詢參數傳遞授權金鑰(Authorization)、地點名稱(locationName)、和天氣因子(elementName)來查詢指定縣市的天氣資料。
ApiClient.Java

public class ApiClient {
    public Retrofit myWeatherApi() {
        return new Retrofit.Builder()
                .baseUrl("https://opendata.cwa.gov.tw/api/v1/rest/datastore/")
                .addConverterFactory(GsonConverterFactory.create())
       .addCallAdapterFactory(retrofit2.adapter.rxjava3.RxJava3CallAdapterFactory.create())
                .build();
    }
}

MainActivity.Java

public class MainActivity extends AppCompatActivity {

    private TextView result;
    private Button search;
    private Spinner time_spinner, element_spinner, location_spinner;
    private String selected_time, selected_element, selected_location;
    private ApiClient apiClient;
    private GetApi getApi;
    private String[] location_data, element_data, time_data, tw_element;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        result = findViewById(R.id.main_result_rt);
        search = findViewById(R.id.main_search_btn);
        time_spinner = findViewById(R.id.time);
        element_spinner = findViewById(R.id.elementName);
        location_spinner = findViewById(R.id.locationName);
        apiClient = new ApiClient();
        getApi = apiClient.myWeatherApi().create(GetApi.class);
        location_data = getResources().getStringArray(R.array.location_data);
        element_data = getResources().getStringArray(R.array.element_data);
        time_data = getResources().getStringArray(R.array.time_data);
        tw_element = getResources().getStringArray(R.array.tw_element);

        setSpinner();
        search.setOnClickListener(view -> getWeather(selected_location, selected_element, selected_time));
    }

    private void setSpinner() {
        ArrayAdapter location_adapter = new ArrayAdapter(this, android.R.layout.simple_spinner_dropdown_item, location_data);
        ArrayAdapter element_adapter = new ArrayAdapter(this, android.R.layout.simple_spinner_dropdown_item, element_data);
        ArrayAdapter time_adapter = new ArrayAdapter(this, android.R.layout.simple_spinner_dropdown_item, time_data);


        location_spinner.setAdapter(location_adapter);
        element_spinner.setAdapter(element_adapter);
        time_spinner.setAdapter(time_adapter);


        location_spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
            @Override
            public void onItemSelected(AdapterView<?> adapterView, View view, int i, long l) {
                selected_location = location_spinner.getSelectedItem().toString();
            }

            @Override
            public void onNothingSelected(AdapterView<?> adapterView) {

            }
        });

        element_spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
            @Override
            public void onItemSelected(AdapterView<?> adapterView, View view, int i, long l) {
                selected_element = element_spinner.getSelectedItem().toString();
            }

            @Override
            public void onNothingSelected(AdapterView<?> adapterView) {

            }
        });
        time_spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
            @Override
            public void onItemSelected(AdapterView<?> adapterView, View view, int i, long l) {
                selected_time = time_spinner.getSelectedItem().toString();
            }

            @Override
            public void onNothingSelected(AdapterView<?> adapterView) {

            }
        });
    }

    private void getWeather(String selectedLocation, String selectedElement, String selectedTime) {
        String authorization = "CWA-E7B0471C-F45E-4B28-9354-5953C80D9416";
        if (selectedElement.equals("All")) selectedElement = "";
        String finalSelectedElement = selectedElement;


        getApi.getWeatherApi(authorization, selectedLocation, selectedElement)
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new DisposableObserver<weatherResponse>(){
                    @Override
                    public void onNext(@NonNull weatherResponse weatherResponse) {
                        result.setText("");

                        List<String> time_list = Arrays.asList(time_data);
                        List<String> element_list = Arrays.asList(element_data);

                        try {
                            if (weatherResponse.getElementSize() != 1) {
                                for (int i = 0; i < weatherResponse.getElementSize(); i++) {
                                    result.append(tw_element[i] + weatherResponse.getDataByTime(i, time_list.indexOf(selectedTime)) + "\n");

                                }
                            } else {
                                result.setText(tw_element[element_list.indexOf(finalSelectedElement)] + weatherResponse.getDataByTime(0, time_list.indexOf(selectedTime)) + "\n");
                            }
                        } catch (Exception e) {
                            Log.e("test", "onNext: " + e);
                        }
                    }

                    @Override
                    public void onError(@NonNull Throwable e) {
                        Log.d("test", "onError: ");
                    }

                    @Override
                    public void onComplete() {
                        Log.d("test", "onComplete: ");
                    }
                });
    }
}

顯示成果:


上一篇
# Day 27 天氣API(上)
下一篇
# Day 29 MVP、MVC 架構
系列文
當Java遇見Android,30天學習指南30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言